package com.kit.lib;

import java.util.Arrays;

/**
 * Aes10加密工具类，密钥请见 接口文档，偏移量和密钥相同 这个非标准加密算法
 * 
 * @author Yuanqy
 * @Date 2019/07/05
 * @demo https://www.it399.com/aes
 * @company http://www.lianyuplus.com/
 */
public class AES10 {
	// 修改S盒模型，IV盒模型，行列偏移模型
	// private final char Sbox[] = {6, 8, 4, 9, 7, 2, 1, 0, 5, 3};
	// private final char InvSbox[] = {5, 7, 2, 8, 9, 3, 0, 1, 4, 6};
	// private final char rc[] = {0, 2, 4, 6, 8, 9, 7, 5, 3, 1};
	private final int Sbox[] = {
			/* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
			0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, /* 0 */
			0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, /* 1 */
			0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, /* 2 */
			0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, /* 3 */
			0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, /* 4 */
			0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, /* 5 */
			0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, /* 6 */
			0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, /* 7 */
			0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, /* 8 */
			0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, /* 9 */
			0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, /* a */
			0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, /* b */
			0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, /* c */
			0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, /* d */
			0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, /* e */
			0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 /* f */
	};
	private final int rc[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 };

	private char m_key[] = new char[16]; // key 密钥 len=Nx4
	private char m_iv[] = new char[16];// iv偏移量 len=Nx4

	private int w[][][] = new int[11][4][4];// 三维，用户行列偏移量,通过密钥key得来

	public static void main(String[] args) {

		char[] input = "213098455".toCharArray(); // 数据内容，必须为数字
		char[] ivs = "UCG6F78XpLoFcUwk".toCharArray(); // iv 和 key,长度必须为16，可以是字母数字
		char[] key = "UCG6F78XpLoFcUwk".toCharArray();

		AES10 aes = new AES10(key, ivs);
		// =========================================
		char _pwd[] = aes.Encrypt(input, input.length);
		System.out.println("加" + String.valueOf(_pwd));
		// ==========================================
		char _input[] = aes.Decrypt(_pwd, _pwd.length);
		System.out.println("解" + String.valueOf(_input));

	}

	public AES10(char[] m_key, char[] m_iv) {
		this.m_key = m_key;
		this.m_iv = m_iv;
		SetKey(this.m_key);
	}

	// 设置密钥
	public void SetKey(char[] key) {
		KeyExpansion(key, w);
		for (int i = 0; i < 11; i++) {
			for (int c = 0; c < 4; c++) {
				for (int r = 0; r < 4; r++) {
					System.out.print(" " + Integer.toHexString(w[i][r][c]).toUpperCase());
				}
				System.out.println("");
			}
			System.out.println(" ");
		}
	}

	public char[] Cipher(char[] input, char[] output) {
		// System.err.println(input);
		int state[][] = new int[4][4];
		int i, r, c;
		for (r = 0; r < 4; r++)
			for (c = 0; c < 4; c++)
				state[r][c] = input[c * 4 + r];

		AddRoundKey(state, w[0]); // 轮密钥加
		for (i = 1; i <= 10; i++) {
			SubBytes(state); // 字节替换
			ShiftRows(state); // 行位移
			if (i != 10)
				MixColumns(state); // 列混合
			AddRoundKey(state, w[i]); // 轮密钥加
		}

		for (r = 0; r < 4; r++)
			for (c = 0; c < 4; c++)
				output[c * 4 + r] = (char) state[r][c];
		// print(output, 16);
		return output;
	}

	public void KeyExpansion(char[] key, int w[][][]) {
		int i, j, r, c;
		for (r = 0; r < 4; r++)
			for (c = 0; c < 4; c++)
				w[0][r][c] = key[r + c * 4];

		for (i = 1; i <= 10; i++) {
			for (j = 0; j < 4; j++) {
				int t[] = new int[4];
				for (r = 0; r < 4; r++)
					t[r] = j > 0 ? w[i][r][j - 1] : w[i - 1][r][3];

				if (j == 0) {
					int temp = t[0];
					for (r = 0; r < 3; r++) {
						t[r] = Sbox[t[(r + 1) % 4]];
					}
					t[3] = Sbox[temp];
					t[0] ^= rc[i - 1];
				}
				for (r = 0; r < 4; r++)
					w[i][r][j] = (char) ((w[i - 1][r][j] ^ t[r]));
			}
		}
	}

	public char FFmul(int j, int b) {
		int bw[] = new int[4];
		char res = 0;
		char i;
		bw[0] = b;
		for (i = 1; i < 4; i++) {
			bw[i] = (bw[i - 1] << 1);
			if ((bw[i - 1] & 0x80) > 0)
				bw[i] ^= 0x1b;
		}
		for (i = 0; i < 4; i++) {
			if (((j >> i) & 0x01) > 0)
				res ^= bw[i];
		}
		res = (char) (res % 256);
		System.out.println("FFmul[" + j + "," + new Integer((b)) + "]:\t" + new Integer((res)));
		return res;
	}

	public void SubBytes(int state[][]) {
		int r, c;
		for (r = 0; r < 4; r++) {
			for (c = 0; c < 4; c++)
				state[r][c] = Sbox[state[r][c]];
		}
	}

	public void ShiftRows(int state[][]) {
		int t[] = new int[4];
		int r, c;
		for (r = 1; r < 4; r++) {
			for (c = 0; c < 4; c++)
				t[c] = state[r][(c + r) % 4];
			for (c = 0; c < 4; c++)
				state[r][c] = t[c];
		}
	}

	public void MixColumns(int state[][]) {
		char t[] = new char[4];
		int r, c;
		for (c = 0; c < 4; c++) {
			for (r = 0; r < 4; r++)
				t[r] = (char) state[r][c];
			for (r = 0; r < 4; r++)
				state[r][c] = (FFmul(0x02, t[r]) ^ FFmul(0x03, t[(r + 1) % 4]) ^ FFmul(0x01, t[(r + 2) % 4])
						^ FFmul(0x01, t[(r + 3) % 4]));
		}
	}

	public void AddRoundKey(int state[][], int k[][]) {
		int r, c;
		for (c = 0; c < 4; c++)
			for (r = 0; r < 4; r++) {
				// if(state[r][c]>=255)//注意越界
				// state[r][c]=0;
				System.err.println(state[r][c] + "^^^" + k[r][c]);
				state[r][c] = ((state[r][c] ^ k[r][c]) % 10);
				System.err.println("AddRoundKey:" + state[r][c]);
			}

	}

	/**************************************************************/

	public char[] Encrypt(char[] _in, int _length) {
		for (int x = 0; x < _length / 2; x++) {
			if (x % 2 == 0) {
				_in[x] ^= _in[_length - 1 - x];
				_in[_length - 1 - x] ^= _in[x];
				_in[x] ^= _in[_length - 1 - x];
			}
		}
		System.err.println(new String(_in));
		char[] _out = new char[_length];
		int first_round = 1;
		int rounds = 0;
		int start = 0;
		int end = 0;
		char input[] = new char[16];
		char output[] = new char[16];
		char ciphertext[] = new char[16];
		char cipherout[] = new char[256];
		char[] plaintext = new char[16];
		int co_index = 0;
		// 1. get rounds
		if (_length % 16 == 0)
			rounds = _length / 16;
		else
			rounds = _length / 16 + 1;

		int point = 0;
		// 2. for all rounds
		for (int j = 0; j < rounds; ++j) {
			start = j * 16;
			end = j * 16 + 16;
			if (end > _length)
				end = _length; // end of input
			// 3. copyt input to m_plaintext
			point += start;
			plaintext = Arrays.copyOfRange(_in, point, end - start);
			// 4. handle all modes
			// MODE_CFB
			{
				if (first_round == 1) {
					output = Cipher(m_iv, output);
					first_round = 0;
				} else {
					output = Cipher(input, output);
				}
				// print(output, 16);
				for (int i = 0; i < 16; ++i) {
					if ((end - start) - 1 < i) {
						ciphertext[i] = (char) (0 ^ output[i]);
					} else {
						System.err.println(Integer.toHexString(output[i]) + "---" + Integer.toHexString(ciphertext[i]));
						int b = (output[i] * (i > 0 ? ciphertext[i - 1] : 9)) % 10;
						ciphertext[i] = (char) ((plaintext[i] + 2 + b) % 10 + 0x30);
						System.out.println("p:" + ciphertext[i] + "- x:" + b + "");
					}
				}
				for (int k = 0; k < end - start; ++k) {
					cipherout[co_index++] = ciphertext[k];
				}
				input = Arrays.copyOfRange(ciphertext, 0, 16);
			}
		}
		_out = Arrays.copyOfRange(cipherout, 0, co_index);
		return _out;
	}

	public char[] Decrypt(char[] _in, int _length) {
		char[] _out = new char[_length];
		int first_round = 1;
		int rounds = 0;
		char ciphertext[] = new char[16];
		char input[] = new char[16];
		char output[] = new char[16];
		char plaintext[] = new char[16];
		char plainout[] = new char[256];
		int po_index = 0;
		if (_length % 16 == 0) {
			rounds = _length / 16;
		} else {
			rounds = _length / 16 + 1;
		}

		int start = 0;
		int end = 0;
		int point = 0;
		for (int j = 0; j < rounds; j++) {
			start = j * 16;
			end = start + 16;
			if (end > _length) {
				end = _length;
			}
			point += start;
			ciphertext = Arrays.copyOfRange(_in, point, end - start);

			// MODE_CFB
			{
				if (first_round == 1) {
					Cipher(m_iv, output);
					first_round = 0;
				} else {
					Cipher(input, output);
				}
				for (int i = 0; i < 16; i++) {
					// ##############################
					if (i < _length) {
						int c = (ciphertext[i] - 0x30);
						int b = (output[i] * (i > 0 ? ciphertext[i - 1] : 9)) % 10;
						plaintext[i] = (char) ((c >= b ? c - b : c - b + 10) + 0x30);
					}
				}
				for (int k = 0; k < end - start; ++k) {
					plainout[po_index++] = plaintext[k];
				}
				input = Arrays.copyOfRange(ciphertext, 0, 16);
			}
		}
		_out = Arrays.copyOfRange(plainout, 0, po_index);
		for (int x = 0; x < _length / 2; x++) {
			if (x % 2 == 0) {
				_out[x] ^= _out[_length - 1 - x];
				_out[_length - 1 - x] ^= _out[x];
				_out[x] ^= _out[_length - 1 - x];
			}
		}
		return _out;
	}

	public void print(char[] state, int len) {
		int i;
		for (i = 0; i < len; i++) {
			if (state[i] < 16)
				System.out.print(" 0" + Integer.toHexString((state[i] & 0xff)).toUpperCase());
			else
				System.out.print(" " + Integer.toHexString(state[i] & 0xff).toUpperCase());
		}
		System.out.println("\n");
	}

}
